﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

using MoreLinq;

namespace LaunchPad.Core.Maths
{
    public static class Clustering
    {
        public static double Distance(this Point point, Point other)
        {
            return Math.Sqrt(Math.Pow(other.X - point.X, 2.0) + Math.Pow(other.Y - point.Y, 2.0));
        }

        public static IEnumerable<Cluster<T>> Cluster<T>(this IEnumerable<T> items, Func<T, Point> pointSelector, int clusterCount, int maxClusterSize)
        {
            var clusterSize = (int)Math.Ceiling((double)items.Count() / clusterCount);

            int maxItems = clusterCount * maxClusterSize;

            if (!items.Any())
                return Enumerable.Empty<Cluster<T>>();

            var clusters = items.Take(maxItems).Batch(clusterSize).Select(g => new Cluster<T>(g)).ToList();

            int movements = 1;
            while (movements > 0)
            {
                movements = 0;

                foreach (var cluster in clusters)
                {
                    var points = cluster.Select(pointSelector);
                    if(points.Any())
                        cluster.Centroid = points.PointAverage();
                }

                foreach (var cluster in clusters)
                {
                    var removedItems = new List<T>();
                    foreach (var item in cluster)
                    {
                        Point point = pointSelector(item);
                        var nearestCluster = clusters.OrderBy(g => g.Centroid.Distance(point)).First();

                        /* Check if item has moved */
                        if (!nearestCluster.Contains(item))
                        {
                            /* Each cluster must have at least one point and not more than max size */
                            if (cluster.Count() > 1 && nearestCluster.Count < maxClusterSize)
                            {
                                removedItems.Add(item);
                                nearestCluster.Add(item);
                                movements++;
                            }
                        }
                    }

                    foreach (var removedItem in removedItems)
                        cluster.Remove(removedItem);
                }
            }

            foreach (var cluster in clusters)
            {
                var points = cluster.Select(pointSelector);
                if (points.Any())
                    cluster.Centroid = points.PointAverage();
            }

            return clusters;
        }
    }
}
